﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="NHibernateProvider.cs" company="DissingNelleman">
//   
// All rights reserved. </copyright>
// <summary>
//   The n hibernate provider.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace ServiceLibrary.Hibernate
{
    using System;
    using System.Configuration;
    using System.Linq;
    using System.Reflection;

    using log4net;

    using NHibernate;
    using NHibernate.Cfg;
    using NHibernate.Cfg.MappingSchema;
    using NHibernate.Dialect;
    using NHibernate.Mapping.ByCode;
    using NHibernate.Tool.hbm2ddl;

    using Configuration = NHibernate.Cfg.Configuration;

    /// <summary>
    /// The n hibernate provider.
    /// </summary>
    public class NHibernateProvider
    {
        #region Fields

        /// <summary>
        /// The log.
        /// </summary>
        private readonly ILog log;

        /// <summary>
        /// The session factory.
        /// </summary>
        private ISessionFactory sessionFactory;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes static members of the <see cref="NHibernateProvider"/> class.
        /// </summary>
        static NHibernateProvider()
        {
            Instance = new NHibernateProvider();
        }

        /// <summary>
        /// Prevents a default instance of the <see cref="NHibernateProvider"/> class from being created.
        /// </summary>
        private NHibernateProvider()
        {
            this.log = LogManager.GetLogger(typeof(NHibernateProvider));
            this.MapModelComponents();
            this.log.Debug("Created new instance of NHibernateProvider.");
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the instance.
        /// </summary>
        public static NHibernateProvider Instance { get; private set; }

        /// <summary>
        /// Gets the configuration.
        /// </summary>
        public Configuration Configuration { get; private set; }

        /// <summary>
        /// Gets a value indicating whether is db valid.
        /// </summary>
        public bool IsDbValid { get; private set; }

        /// <summary>
        /// Gets the session factory.
        /// </summary>
        public ISessionFactory SessionFactory
        {
            get
            {
                if (this.sessionFactory == null)
                {
                    this.sessionFactory = this.Configuration.BuildSessionFactory();
                }

                return this.sessionFactory;
            }
        }

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// The update db.
        /// </summary>
        public void UpdateDb()
        {
            var su = new SchemaUpdate(this.Configuration);
            su.Execute(false, true);

            if (su.Exceptions.Count > 0)
            {
                foreach (Exception ex in su.Exceptions)
                {
                    this.log.Error(string.Empty, ex);
                }
            }
        }

        /// <summary>
        /// The with session.
        /// </summary>
        /// <param name="action">
        /// The action.
        /// </param>
        public void WithSession(Action<ISession> action)
        {
            using (ISession session = this.SessionFactory.OpenSession())
            {
                action(session);
            }
        }

        /// <summary>
        /// The with session.
        /// </summary>
        /// <param name="action">
        /// The action.
        /// </param>
        /// <typeparam name="T">
        /// The return type of the action.
        /// </typeparam>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        public T WithSession<T>(Func<ISession, T> action)
        {
            T result = default(T);

            this.WithSession(session => { result = action(session); });

            return result;
        }

        /// <summary>
        /// The with session and transaction.
        /// </summary>
        /// <param name="action">
        /// The action.
        /// </param>
        public void WithSessionAndTransaction(Action<ISession> action)
        {
            using (ISession session = this.SessionFactory.OpenSession())
            using (ITransaction tx = session.BeginTransaction())
            {
                action(session);
                tx.Commit();
            }
        }

        /// <summary>
        /// The with session and transaction.
        /// </summary>
        /// <param name="action">
        /// The action.
        /// </param>
        /// <typeparam name="T">
        /// The return type of the action.
        /// </typeparam>
        /// <returns>
        /// The <see cref="T"/>.
        /// </returns>
        public T WithSessionAndTransaction<T>(Func<ISession, T> action)
        {
            T result = default(T);

            this.WithSessionAndTransaction(session => { result = action(session); });

            return result;
        }

        #endregion

        #region Methods

        /// <summary>
        /// The create configuration.
        /// </summary>
        private void CreateConfiguration()
        {
            this.Configuration = new Configuration();
            this.Configuration.DataBaseIntegration(
                c =>
                    {
                        c.Dialect<MsSql2005Dialect>();
                        c.ConnectionString = ConfigurationManager.ConnectionStrings["db"].ConnectionString;
                        c.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
                        c.SchemaAction = SchemaAutoAction.Validate;
                    });
        }

        /// <summary>
        /// The map model components.
        /// </summary>
        private void MapModelComponents()
        {
            var mapper = new ModelComponentModelMapper();

            this.CreateConfiguration();

            foreach (
                Type t in
                    Assembly.GetExecutingAssembly()
                            .GetTypes()
                            .Where(t => t.GetInterfaces().Any(i => i == typeof(IConformistHoldersProvider))))
            {
                mapper.AddMapping(t);
            }

            // Compile mappings
            HbmMapping hbmMapping = mapper.CompileMappingForAllExplicitlyAddedEntities();

            this.Configuration.AddMapping(hbmMapping);

            try
            {
                var sv = new SchemaValidator(this.Configuration);
                sv.Validate();
                this.IsDbValid = true;
            }
            catch (Exception ex)
            {
                // Not ok
                this.IsDbValid = false;

                // Log
                this.log.Error(ex.Message, ex);
            }
        }

        #endregion
    }
}